/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement the log facility for AMCC.
 *
 * @author  WXQ#XDU
 * @date    2024.11
 */


#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "../include/amcc_log.h"
#include "../include/amcc_error.h"
#include "../include/amcc_string.h"
#include "../include/amcc_time.h"


// Descriptor type of log message.
typedef struct {
    EnumLogLevel logLevel;     // local level for this log file
    int          srcLineNo;    // caller line no
    const char * srcFileName;  // caller filename
    const char * formatString; // format string
    va_list      argList;      // list of arguments for format string
    FILE *       fpLog;        // handler of log file
    t_time       currentTime;
} t_log_msg;

#define LOGFLAG_HEADER 1  // log with full header

// Type of output functions bound to log appender
typedef void (* t_log_function)(t_log_msg *, int flag);

// Type of log appender which may be a log file,
// the stdout, or the stderr.
typedef struct {
    EnumLogLevel   logLevel;      // local log Level
    char *         filePath;      // copy of file path
    FILE *         fpLog;         // handler of log file
    t_log_function funcOutput;    // function to write log text
} t_log_appender;


#define MAX_SIZE_APPENDER  10 // max number of appenders

typedef struct {
    int  nAppenders;
    t_log_appender allAppenders[MAX_SIZE_APPENDER];
} t_log_appenders;

static t_log_appenders globAppenders;

static const char * log_level_names[] = {
    "ERR-LVL",
    "TRACE",
    "DEBUG",
    "INFO",
    "WARN",
    "ERROR",
    "FATAL"
};


// initialize inner data of log module
int log_module_init() {
    memset(&globAppenders, 0, sizeof(globAppenders));
    log_add_appender(amcc_get_logLevel(),
                     amcc_get_logFile());
    return 0;
}

// clean inner data of log module
void log_module_clean() {
    for (int i = 0; i < globAppenders.nAppenders; ++i) {
        if (NULL != globAppenders.allAppenders[i].fpLog) {
            fclose( globAppenders.allAppenders[i].fpLog );
            globAppenders.allAppenders[i].fpLog = NULL;
        }
        if (NULL != globAppenders.allAppenders[i].filePath) {
            cstring_destroy(globAppenders.allAppenders[i].filePath);
            globAppenders.allAppenders[i].filePath = NULL;
        }
    }
    globAppenders.nAppenders = 0;
}



#define LOG_INNER_IMPL(flag, level, srcfile, srcline, valist, format)       \
do { t_log_msg  logmsgItem;                                                 \
    logmsgItem.logLevel     = level;                                        \
    logmsgItem.srcFileName  = srcfile;                                      \
    logmsgItem.srcLineNo    = srcline;                                      \
    logmsgItem.formatString = format;                                       \
    logmsgItem.currentTime  = amcc_get_time();                              \
    for (int i = 0; i < globAppenders.nAppenders; i++) {                    \
        t_log_appender * appender = &(globAppenders.allAppenders[i]);       \
        if ((NULL == appender->funcOutput) || ((level) < appender->logLevel)) \
            continue;                                                       \
        logmsgItem.fpLog = appender->fpLog;                                 \
        LOG_INNER_IMPL_OPENVAARG(format);                                   \
        appender->funcOutput( &logmsgItem , flag );                         \
        LOG_INNER_IMPL_CLOSEVAARG;                                          \
    }  \
} while(0)

#define LOG_INNER_IMPL_OPENVAARG(format)  va_start( logmsgItem.argList, format )
#define LOG_INNER_IMPL_CLOSEVAARG         va_end( logmsgItem.argList )

void log_implement(EnumLogLevel level, const char *srcfile, int srcline,
                   const char *format, ...)
{
    LOG_INNER_IMPL(LOGFLAG_HEADER, level, srcfile, srcline, NULL, format);
}


void log_without_header(const char * format, ...)
{
    LOG_INNER_IMPL(0, LOG_INFO, NULL, 0, NULL, format);
}


#undef  LOG_INNER_IMPL_OPENVAARG
#undef  LOG_INNER_IMPL_CLOSEVAARG
#define LOG_INNER_IMPL_OPENVAARG(format)  logmsgItem.argList = valist
#define LOG_INNER_IMPL_CLOSEVAARG

void log_implement_valist(EnumLogLevel level, const char *srcfile, int srcline,
                   const char *format, va_list  valist)
{
    LOG_INNER_IMPL(LOGFLAG_HEADER, level, srcfile, srcline, valist, format);
}


// default handler that writes message to a file
static void logfile_output(t_log_msg *logmsg, int flag) {
    if ( (flag & 1) != 0 ) {
        if (amcc_is_logTime() ) {
            fprintf(logmsg->fpLog, "%04d-%02d-%02d %02d:%02d:%02d.%03d " ,
                    logmsg->currentTime.tm.tm_year,
                    logmsg->currentTime.tm.tm_mon, logmsg->currentTime.tm.tm_mday,
                    logmsg->currentTime.tm.tm_hour, logmsg->currentTime.tm.tm_min,
                    logmsg->currentTime.tm.tm_sec, logmsg->currentTime.tv_msec );
        }
        fprintf(logmsg->fpLog, "[%-5s] ", log_level_names[logmsg->logLevel] );
        if (amcc_is_logSource() ) {
            fprintf(logmsg->fpLog, "[%-12.12s:%3d] ",
                    path_basename(logmsg->srcFileName),
                    logmsg->srcLineNo);
        }
    }
    vfprintf(logmsg->fpLog, logmsg->formatString, logmsg->argList);
    fprintf (logmsg->fpLog, "\n");
    fflush(logmsg->fpLog);
}


int log_add_appender(EnumLogLevel level, const char* filepath) {
    if (globAppenders.nAppenders >= MAX_SIZE_APPENDER)
        return -1;

    // DON'T add repeatedly, only check string -> filepath
    for (int i = 0; i < globAppenders.nAppenders; ++i) {
        if (0 == strcmp(filepath, globAppenders.allAppenders[i].filePath))
            return 0;
    }

    {   // prepare directory specified by filepath
        char dir[FILEPATH_MAXSIZE + 1];
        path_dirname(filepath, dir, sizeof(dir));
        if('\0' != dir[0] ) path_create_dir(dir );
    }
    FILE * fpLog = fopen(filepath, "w+");
    if (NULL == fpLog) {
        ERROR_REPORT_FILEOP(EC_OPENFILE, filepath);
        return -2;
    }

    if( level < LOG_TRACE || level > LOG_FATAL )
        level = amcc_get_logLevel();

    t_log_appender * appender = &(globAppenders.allAppenders[globAppenders.nAppenders]);
    appender->fpLog      = fpLog;
    appender->logLevel   = level;
    appender->funcOutput = logfile_output;
    appender->filePath   = cstring_duplicate(filepath);
    ++ globAppenders.nAppenders;

    //tag: add header lines for each log files
    fprintf(fpLog,"Welcome to [%s] based on lib" AMCC_PRJ_NAME " v"
             AMCC_VERSION_STRING "(%s) from %s.\n",
            amcc_get_appname(), amcc_build_info(),
            amcc_organization());
    fprintf(fpLog, "Current time is %s\n" , amcc_get_timestring(NULL));
    fprintf(fpLog, "Current log-level is %s\n", log_level_names[level]);
    return 0;
}
